﻿using System;
using System.Linq;
using System.Security.Cryptography;
using System.Text;
using System.Threading.Tasks;

using Microsoft.AspNetCore.SignalR.Client;
using Microsoft.Extensions.Configuration;
using Microsoft.Extensions.Hosting;

using MTP.Serialize;

namespace MicroCenter.Core
{
    class MicroCenterRegistry : IMTPRegistry
    {
        private readonly HubConnection hubConnection;
        private RegistryModel registryModel;
        private readonly string applicationName;
        private readonly IConfiguration configuration;

        public MicroCenterRegistry(IConfiguration configuration, IHostEnvironment hostEnvironment)
        {
            this.applicationName = hostEnvironment.ApplicationName;
            this.configuration = configuration;
            this.hubConnection = new HubConnectionBuilder()
                .WithAutomaticReconnect(new RetryPolicy())
                .WithUrl(BuilderUri())
                .Build();
            this.hubConnection.On<RegistryModel[]>("ReLoad", Call);
            this.hubConnection.Reconnected += Hub_Reconnected;

        }


        public async Task Connect()
        {
            try
            {
                await hubConnection.StartAsync();
            }
            catch (Exception e)
            {
                Console.WriteLine(e.Message);
                throw new Exception("MicroCenter registryUrl invalid");
            }

        }

        public async Task Registry(int port, string cipher, string[] serviceNames)
        {
            try
            {
                string urls = configuration["urls"];
                registryModel = new RegistryModel(urls, applicationName, port, cipher, serviceNames);
                await Hub_Reconnected("Connected");
            }
            catch (Exception e)
            {
                Console.WriteLine(e.Message);
                throw new Exception("MicroCenter secret invalid");
            }
        }

        private string BuilderUri()
        {
            string registryUrl = configuration["MicroCenter:registryUrl"];

            string secret = configuration["MicroCenter:secret"];
            if (string.IsNullOrEmpty(registryUrl))
            {
                throw new ArgumentNullException("MicroCenter:registryUrl");
            }
            if (string.IsNullOrEmpty(secret))
            {
                throw new ArgumentNullException("MicroCenter:secret");
            }
            var t = DateTimeOffset.Now.ToUnixTimeSeconds();
            string accessToken = "";
            using (MD5 md5 = MD5.Create())
            {
                byte[] bytes = md5.ComputeHash(Encoding.UTF8.GetBytes(t + secret));
                StringBuilder sb = new StringBuilder();
                foreach (var i in bytes)
                {
                    sb.Append(i.ToString("x2"));
                }
                accessToken = sb.ToString().ToUpper();
            }
            return $"{registryUrl.TrimEnd('/')}/api/ws/micro?t={t}&access_token={accessToken}";
        }


        public event Action<RemoteOptions[]> Reload;
        public void Call(RegistryModel[] registryModels)
        {
            Reload?.Invoke(registryModels.Where(a => a.ServiceName != applicationName && !string.IsNullOrEmpty(a.Information)).Select(a => a.Builder()).ToArray());
        }

        private async Task Hub_Reconnected(string arg)
        {
            await this.hubConnection.InvokeAsync("Registry", registryModel);
        }
        class RetryPolicy : IRetryPolicy
        {
            public TimeSpan? NextRetryDelay(RetryContext retryContext)
            {
                return retryContext.PreviousRetryCount switch
                {
                    0 => TimeSpan.FromSeconds(1),
                    1 => TimeSpan.FromSeconds(5),
                    2 => TimeSpan.FromSeconds(10),
                    3 => TimeSpan.FromSeconds(30),
                    _ => TimeSpan.FromSeconds(60)
                };
            }
        }

    }
 
    class RegistryModel
    {
        public RegistryModel()
        {

        }

        public RegistryModel(string urls, string serviceName, int port, string cipher, string[] serviceNames)
        {
            this.ProxyUrl = urls;
            this.Information = port <= 0 ? "" : $"{port}/{cipher},{string.Join(',', serviceNames)}";
            this.ServiceName = serviceName;
        }

        public string ServiceName { get; set; }

        public string Information { get; set; }

        public string ProxyUrl { get; set; }

        public string RemoteIP { get; set; }

        public RemoteOptions Builder()
        {
            var array = Information.Split(",");
            return new RemoteOptions($"mtp://{RemoteIP}:{array[0]}", array.Skip(1).ToArray());
        }
    }
}
